﻿using K9Nano.Dependency;
using K9Nano.Exceptions;
using K9Nano.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace K9Nano.Domain.Organization;

public abstract class OrganizationUnitManagerBase<TDbContext>(TDbContext db) : IOrganizationUnitManager where TDbContext : DbContext
{
    public virtual async Task<OrganizationUnit?> GetAsync(long id, CancellationToken cancellation)
    {
        return await db.Set<OrganizationUnit>().AsNoTracking()
            .FirstOrDefaultAsync(a => a.Id == id, cancellation);
    }

    public virtual async Task CreateAsync(OrganizationUnit organizationUnit, CancellationToken cancellation)
    {
        organizationUnit.Code = await GetNextChildCodeAsync(organizationUnit.ParentId, cancellation);
        await ValidateOrganizationUnitAsync(organizationUnit, cancellation);
        db.Add(organizationUnit);
        await db.SaveChangesAsync(cancellation);
    }

    public virtual async Task UpdateAsync(OrganizationUnit organizationUnit, CancellationToken cancellation)
    {
        await ValidateOrganizationUnitAsync(organizationUnit, cancellation);
        var entity = await db.Set<OrganizationUnit>().FirstOrDefaultAsync(a => a.Id == organizationUnit.Id, cancellation)
            ?? throw FriendlyException.BadRequest("组织机构不存在");
        entity.DisplayName = organizationUnit.DisplayName;
        entity.Tag = organizationUnit.Tag;
        await db.SaveChangesAsync(cancellation);
    }

    public virtual async Task<string> GetNextChildCodeAsync(long? parentId, CancellationToken cancellation)
    {
        var lastChild = await GetLastChildOrNullAsync(parentId, cancellation);
        if (lastChild == null)
        {
            var parentCode = parentId != null ? await GetCodeAsync(parentId.Value, cancellation) : null;
            return OrganizationUnit.AppendCode(parentCode, OrganizationUnit.CreateCode(1));
        }

        return OrganizationUnit.CalculateNextCode(lastChild.Code);
    }

    public virtual async Task<OrganizationUnit?> GetLastChildOrNullAsync(long? parentId, CancellationToken cancellation)
    {
        var child = await db.Set<OrganizationUnit>().AsNoTracking()
            .Where(ou => ou.ParentId == parentId)
            .OrderByDescending(a => a.Code)
            .FirstOrDefaultAsync(cancellation);
        return child;
    }

    public virtual async Task<string> GetCodeAsync(long id, CancellationToken cancellation)
    {
        var code = await db.Set<OrganizationUnit>()
            .AsNoTracking()
            .Where(a => a.Id == id)
            .Select(a => a.Code)
            .FirstOrDefaultAsync(cancellation)
            ?? throw FriendlyException.BadRequest($"ID为{id}的组织机构不存在");
        return code;
    }

    public virtual async Task DeleteAsync(long id, CancellationToken cancellation)
    {
        await db.Set<OrganizationUnit>()
            .Where(a => a.ParentId == id)
            .ExecuteDeleteAsync(cancellation);
        await db.Set<OrganizationUnit>()
          .Where(a => a.Id == id)
          .ExecuteDeleteAsync(CancellationToken.None);
    }

    public virtual async Task MoveAsync(long id, long? parentId, CancellationToken cancellation)
    {
        var organizationUnit = await db.Set<OrganizationUnit>().FirstOrDefaultAsync(a => a.Id == id, cancellation)
            ?? throw FriendlyException.BadRequest($"ID为{id}的组织机构不存在");

        if (organizationUnit.ParentId == parentId)
        {
            return;
        }

        //Should find children before Code change
        var children = await FindChildrenAsync(id, true, cancellation);

        //Store old code of OU
        var oldCode = organizationUnit.Code;

        //Move OU
        organizationUnit.Code = await GetNextChildCodeAsync(parentId, cancellation);
        organizationUnit.ParentId = parentId;

        await ValidateOrganizationUnitAsync(organizationUnit, cancellation);

        //Update Children Codes
        foreach (var child in children)
        {
            child.Code = OrganizationUnit.AppendCode(organizationUnit.Code,
                OrganizationUnit.GetRelativeCode(child.Code, oldCode));
        }
        await db.SaveChangesAsync(cancellation);
    }

    public virtual async Task<List<OrganizationUnit>> FindChildrenAsync(long? parentId, bool recursive, CancellationToken cancellation)
    {
        if (!recursive)
        {
            return await db.Set<OrganizationUnit>().AsNoTracking()
                .Where(a => a.ParentId == parentId)
                .ToListAsync(cancellation);
        }

        if (!parentId.HasValue)
        {
            return await db.Set<OrganizationUnit>().AsNoTracking()
               .ToListAsync(cancellation);
        }

        var code = await GetCodeAsync(parentId.Value, cancellation);

        return await db.Set<OrganizationUnit>().AsNoTracking()
                .Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value)
                .ToListAsync(cancellation);
    }

    protected virtual async Task ValidateOrganizationUnitAsync(OrganizationUnit organizationUnit, CancellationToken cancellation)
    {
        bool invalid;
        if (!organizationUnit.ParentId.HasValue)
        {
            invalid = await db.Set<OrganizationUnit>()
                .AnyAsync(x => x.DisplayName == organizationUnit.DisplayName, cancellation);
        }
        else
        {
            invalid = await db.Set<OrganizationUnit>()
                .AnyAsync(a => a.ParentId == organizationUnit.ParentId
                               && a.Id != organizationUnit.Id
                               && a.DisplayName == organizationUnit.DisplayName, cancellation);
        }

        if (invalid)
        {
            throw FriendlyException.BadRequest("组织机构重复: " + organizationUnit.DisplayName);
        }
    }

    public virtual async Task<(int Total, OrganizationUnit[] Items)> QueryAsync(string? name,
        long? parentId,
        int pageIndex,
        int pageSize,
        CancellationToken cancellation)
    {
        var query = db.Set<OrganizationUnit>().AsNoTracking()
            .WhereIf(parentId > 0, a => a.ParentId == parentId!)
            .WhereIf(!string.IsNullOrEmpty(name), a => a.DisplayName.Contains(name!));
        var total = await query.CountAsync(cancellation);
        if (total <= 0) return (0, []);
        var items = await query
            .OrderBy(x => x.Id)
            .Skip((pageIndex - 1) * pageSize)
            .Take(pageSize)
            .ToArrayAsync(cancellation);
        return (total, items);
    }
}